﻿using Microsoft.Extensions.Logging;
using SkiaSharp;

namespace Devonline.AspNetCore;

/// <summary>
/// 图片缩放服务
/// </summary>
/// <param name="logger"></param>
/// <param name="httpSetting"></param>
public class ImageResizeService(ILogger<ImageResizeService> logger, HttpSetting httpSetting) : BaseImageService(logger, httpSetting), IImageResizeService
{
    /// <summary>
    /// 根据指定分辨率获取缩放后的文件名, 格式: 文件名_分辨率.扩展名
    /// 此方法返回的文件名分以下几种情况:
    /// 1. 如果原图的分辨率小于等于指定分辨率, 则返回原图分辨率, 返回 文件名_原图分辨率.扩展名 作为文件名, 
    ///     eg: 原图分辨率为 1280x720, 指定分辨率为 1280x720, 则返回 文件名_1280x720.扩展名, 因为原图分辨率与指定分辨率相同
    ///     eg: 原图分辨率为 1280x720, 指定分辨率为 1920x1080, 则返回 文件名_1280x720.扩展名
    ///     eg: 原图分辨率为 1280x720, 指定分辨率为 1920x1440, 则返回 文件名_1280x720.扩展名
    /// 2. 如果原图的分辨率大于指定分辨率, 则返回缩放后最接近指定分辨率的分辨率, 缩放方式以宽高的短边为准, 返回 文件名_指定分辨率.扩展名 作为文件名
    ///     eg: 原图分辨率为 1920x1080, 指定分辨率为 1920x1080, 则返回 文件名_1920x1080.扩展名, 因为原图分辨率与指定分辨率相同
    ///     eg: 原图分辨率为 1920x1080, 指定分辨率为 1280x720, 则返回 文件名_1280x720.扩展名, 因为原图分辨率与指定分辨率缩放比例相同
    ///     eg: 原图分辨率为 1920x1440, 指定分辨率为 1280x720, 则返回 文件名_960x720.扩展名, 因为根据原图和指定分辨率的宽高比计算出的最接近指定分辨率的分辨率为 960x720, 因此返回的文件名为 文件名_960x720.扩展名
    ///     eg: 原图分辨率为 1920x1440, 指定分辨率为 1920x1080, 则返回 文件名_1440x1080.扩展名, 因为根据原图和指定分辨率的宽高比计算出的最接近指定分辨率的分辨率为 1440x1080, 因此返回的文件名为 文件名_1440x1080.扩展名
    /// </summary>
    /// <param name="fileName">原始文件名</param>
    /// <param name="width">宽度</param>
    /// <param name="height">高度</param>
    /// <returns></returns>
    /// <exception cref="DirectoryNotFoundException"></exception>
    public string GetResizeFileName(string fileName, int width, int height)
    {
        var fileInfo = new FileInfo(fileName);
        _logger.LogInformation($"{_userAccess}: get resizing image: {fileInfo.FullName} to {width}x{height}");

        using var stream = fileInfo.OpenRead();
        using var data = SKData.Create(stream);
        using var codec = SKCodec.Create(data);
        var imageInfo = codec.Info;
        var size = GetNewSize(width, height, imageInfo.Width, imageInfo.Height);
        _logger.LogInformation($"{_userAccess}: get resizing image: {fileInfo.FullName} to {size.Width}x{size.Height}");
        stream.Close();

        var newFileName = GetNewFileName(fileName, $"{size.Width}x{size.Height}");
        _logger.LogInformation($"{_userAccess}: get resizing image: {newFileName}");
        return newFileName;
    }
    /// <summary>
    /// 制作图片文件裁剪图, 当且仅当只有原图片尺寸大于裁剪图尺寸时才会生成裁剪图
    /// </summary>
    /// <param name="fileName">图片对象</param>
    /// <param name="width">最大宽度</param>
    /// <param name="height">最大高度</param>
    /// <exception cref="FileNotFoundException"></exception>
    public string Resize(string fileName, int width, int height)
    {
        if (!File.Exists(fileName))
        {
            throw new FileNotFoundException("文件不存在!", fileName);
        }

        using var stream = File.OpenRead(fileName);
        using var data = SKData.Create(stream);
        using var codec = SKCodec.Create(data);

        var size = GetNewSize(codec.Info.Width, codec.Info.Height, width, height);
        var newFileName = GetResizeFileName(fileName, size.Width, size.Height);
        if (File.Exists(newFileName))
        {
            logger.LogInformation($"{_userAccess}: the file {newFileName} already exists, skipping resize operation.");
            stream.Close();
            return newFileName;
        }

        var imageInfo = new SKImageInfo(size.Width, size.Height);
        var imageOptions = new SKSamplingOptions(SKFilterMode.Nearest, SKMipmapMode.Nearest);
        using var original = SKBitmap.Decode(codec);
        using var resizedImage = original.Resize(imageInfo, imageOptions);

        stream.Close();
        newFileName = GetResizeFileName(fileName, width, height);
        return Save(resizedImage, newFileName);
    }

    /// <summary>
    /// 使用最大宽高新尺寸计算图像同比例缩放新尺寸
    /// </summary>
    /// <param name="width">原始宽度</param>
    /// <param name="height">原始高度</param>
    /// <param name="maxWidth">最大新宽度</param>
    /// <param name="maxHeight">最大新高度</param>
    /// <returns></returns>
    private static SKSizeI GetNewSize(int width, int height, int maxWidth, int maxHeight)
    {
        if (maxWidth <= 0)
        {
            maxWidth = width;
        }

        if (maxHeight <= 0)
        {
            maxHeight = height;
        }

        float MAX_WIDTH = maxWidth;
        float MAX_HEIGHT = maxHeight;
        var ASPECT_RATIO = MAX_WIDTH / MAX_HEIGHT;

        int newWidth, newHeight;
        float originalWidth = width;
        float originalHeight = height;

        if (originalWidth > MAX_WIDTH || originalHeight > MAX_HEIGHT)
        {
            float factor;
            if (originalWidth / originalHeight > ASPECT_RATIO)
            {
                factor = originalWidth / MAX_WIDTH;
                newWidth = Convert.ToInt32(originalWidth / factor);
                newHeight = Convert.ToInt32(originalHeight / factor);
            }
            else
            {
                factor = originalHeight / MAX_HEIGHT;
                newWidth = Convert.ToInt32(originalWidth / factor);
                newHeight = Convert.ToInt32(originalHeight / factor);
            }
        }
        else
        {
            newWidth = width;
            newHeight = height;
        }

        return new SKSizeI(newWidth, newHeight);
    }
}